using System;
using System.DirectoryServices;

namespace Pegasus.DirectoryServices
{
	/// <summary>
	/// 
	/// </summary>
	public class Directory
	{
		// Local Instance Values
		private DirectoryEntry[] m_entries;

		/// <summary>
		/// Initializes a new instance of the <see cref="T:Directory"/> class.
		/// </summary>
		public Directory()
		{
			DirectoryEntry adRoot = new DirectoryEntry( "LDAP://rootDSE" );

			string domainName = (string) adRoot.Properties[ "rootDomainNamingContext" ].Value;

			string[] distinguishedNames = new string[ 1 ];
			distinguishedNames[ 0 ] = domainName;

			SetupDirectoryEntries( distinguishedNames, null, null );
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="T:Directory"/> class.
		/// </summary>
		/// <param name="username">The username.</param>
		/// <param name="password">The password.</param>
		public Directory( string username, string password )
		{
			DirectoryEntry adRoot = new DirectoryEntry( "LDAP://rootDSE", username, password );

			string domainName = (string) adRoot.Properties[ "rootDomainNamingContext" ].Value;

			string[] distinguishedNames = new string[ 1 ];
			distinguishedNames[ 0 ] = domainName;

			SetupDirectoryEntries( distinguishedNames, username, password );
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="T:Directory"/> class.
		/// </summary>
		/// <param name="distinguishedName">Name of the distinguished to the root DC/OU of the Directory.</param>
		/// <remarks>
		/// The distinguishedName name should be in the following format.
		/// 
		///		DC=mycompany,DC=com
		/// 
		/// </remarks>
		public Directory( string distinguishedName )
		{
			string[] distinguishedNames = new string[ 1 ];
			distinguishedNames[ 0 ] = distinguishedName;

			SetupDirectoryEntries( distinguishedNames, null, null );
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="T:Directory"/> class.
		/// </summary>
		/// <param name="distinguishedName">Name of the distinguished.</param>
		/// <param name="username">The username.</param>
		/// <param name="password">The password.</param>
		public Directory( string distinguishedName, string username, string password )
		{
			string[] distinguishedNames = new string[ 1 ];
			distinguishedNames[ 0 ] = distinguishedName;

			SetupDirectoryEntries( distinguishedNames, username, password );
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="T:Directory"/> class.
		/// </summary>
		/// <param name="distinguishedNames">The distinguished names.</param>
		public Directory( string[] distinguishedNames )
		{
			SetupDirectoryEntries( distinguishedNames, null, null );
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="T:Directory"/> class.
		/// </summary>
		/// <param name="distinguishedNames">The distinguished names.</param>
		/// <param name="username">The username.</param>
		/// <param name="password">The password.</param>
		public Directory( string[] distinguishedNames, string username, string password )
		{
			SetupDirectoryEntries( distinguishedNames, username, password );
		}

		/// <summary>
		/// Gets or sets the directory entry.
		/// </summary>
		/// <value>The directory entry.</value>
		public DirectoryEntry[] Entries
		{
			get
			{
				return m_entries;
			}
		}

		/// <summary>
		/// Gets the user by username.
		/// </summary>
		/// <param name="username">The username.</param>
		/// <returns></returns>
		public DirectoryUser GetUserByUsername( string username )
		{
			// TODO: Use the Global Catalog, it should be much faster.

			string searchFilter = string.Format( "(&(objectClass=User)(objectCategory=Person)(sAMAccountName={0}))", username );

			foreach( DirectoryEntry entry in m_entries )
			{
				DirectorySearcher domainSearch = new DirectorySearcher( entry, searchFilter );
				SearchResult result = domainSearch.FindOne();
				if( result != null )
				{
					return new DirectoryUser( result.GetDirectoryEntry() );
				}
			}
			
			return null;
		}

		/// <summary>
		/// Gets the active users.
		/// </summary>
		/// <returns></returns>
		public DirectoryUserCollection GetActiveUsers()
		{
			// Get the all normal, active (not disabled) users accounts that do not end with a '$' (System/Hidden accounts)
			return GetUsers( "(userAccountControl:1.2.840.113556.1.4.803:=512)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(!(cn=*$))" );
		}

		/// <summary>
		/// Gets the user objects from the directory users using the given filter.
		/// </summary>
		/// <param name="ldapSearchFilter">The LDAP search filter.</param>
		/// <param name="args">The args.</param>
		/// <returns></returns>
		/// <remarks>
		/// The search is already scopped to &amp;(objectClass=User)(objectCategory=Person)
		/// </remarks>
		public DirectoryUserCollection GetUsers( string ldapSearchFilter, params object[] args )
		{
			// TODO: Use the Global Catalog, it should be much faster.

			string subFiler = string.Format( ldapSearchFilter, args );
			string searchFilter = string.Format( "(&(objectClass=User)(objectCategory=Person)({0}))", subFiler );

			DirectoryUserCollection usersCollection = new DirectoryUserCollection();
			foreach( DirectoryEntry entry in m_entries )
			{
				DirectorySearcher domainSearch = new DirectorySearcher( entry, searchFilter );
				usersCollection.AddSearchResult( domainSearch.FindAll() );
			}

			return usersCollection;
		}

		/// <summary>
		/// Setups the directory entries.
		/// </summary>
		/// <param name="distinguishedNames">The distinguished names.</param>
		/// <param name="username">The username.</param>
		/// <param name="password">The password.</param>
		private void SetupDirectoryEntries( string[] distinguishedNames, string username, string password )
		{
			int count = distinguishedNames.Length;
			m_entries = new DirectoryEntry[ count ];

			for( int x = 0; x < count; x++ )
			{
				string ldapPath = "LDAP://" + distinguishedNames[ x ];

				if( string.IsNullOrEmpty( username ) )
				{
					m_entries[ x ] = new DirectoryEntry( ldapPath );
				}
				else
				{
					m_entries[ x ] = new DirectoryEntry( ldapPath, username, password );

					// Get the native AdsObject to force authentication. 
					object obj = m_entries[ x ].NativeObject;
				}
			}
		}
	}
}
